#include <windows.h>
#include <stdio.h>
#include <ctpublic.h>


#define RET_ON_FAIL(ret, msg)	if(ret!=CS_SUCCEED&&!infomessage())return 1;
#define ERR_ON_FAIL(ret, msg)	if(ret!=CS_SUCCEED&&!infomessage())goto error;
#define ER0_ON_FAIL(ret, msg)	if(ret!=CS_SUCCEED&&!infomessage()&&exitonerror)goto error;

#define MAX_VALUE_BUF 32000

typedef struct _columndata{
	char		val[MAX_VALUE_BUF];
	CS_INT		len;
	CS_SMALLINT ind; //-1 if null
}COLUMNDATA;


int linebreak=0;
int colbreak=0;
int exitonerror=0;

int linenumber=0;
int cmdstartline=0;

int errorcount=0;
int lasterror=0;

//returns true if lasterror is information message
int infomessage(){
	switch(lasterror){
		case 2007:return 1;
	}
	errorcount=1;
	return 0;
}


/*
** Handler for server messages. Client-Library will call this
** routine when it receives a message from the server.
*/
CS_RETCODE CS_PUBLIC
servermsg_callback(
CS_CONTEXT         *cp,
CS_CONNECTION      *chp,
CS_SERVERMSG       *msgp)
{
	//if(msgp->severity==10)return (CS_SUCCEED);
	if(msgp->msgnumber==5701)return (CS_SUCCEED);//Changed database context
	if(msgp->severity>10)
		printf(	"Server message %ld line(%ld):\n\t",
		(long)msgp->msgnumber, 
		(long)msgp->line+cmdstartline);
	lasterror=msgp->msgnumber;
	if (msgp->proclen > 0)
		printf("\tProcedure: %s\n\t", msgp->proc);
	printf("%s\n", msgp->text);
	return (CS_SUCCEED);
}

/*
**  Client-Library error handler. This function will be invoked
**  when a Client-Library has detected an error. Before Client-
**  Library routines return CS_FAIL, this handler will be called
**  with additional error information.
*/
CS_RETCODE CS_PUBLIC clientmsg_callback(CS_CONTEXT*ctx, CS_CONNECTION*con, CS_CLIENTMSG*emsgp){
	printf("Client error %ld(%ld):\n",
		(long)CS_NUMBER(emsgp->msgnumber),
		(long)CS_SEVERITY(emsgp->severity));
	printf("\t%s\n", emsgp->msgstring);
	return (CS_SUCCEED);
}


int help(){
	printf("usage: cisql <parameters> <options>\n");
	printf("where <parameters>:\n");
	printf("\t-S<servername>\n");
	printf("\t-U<user>\n");
	printf("\t-P<password>\n");
	printf("\t-D<database>\n");
	printf("\t-i<input_file>\n");
	printf("you must specify all parameters\n\n");
	printf("<options>:\n");
	printf("\t-o<output_file>\n");
	printf("\t-J<client_charset>\n");
	printf("\t-x\tbreak batch on error\n");
	printf("\t-l\tnew line for each row\n");
	printf("\t-t\tuse TAB as column separator\n");
	printf("by default there is no separators");
	return 1;
}

int isGO(char * s){
	int i;
	for(i=0;s[i]!=0&&(s[i]==' '||s[i]=='\t');i++);
	if(s[i]!='G'&&s[i]!='g')return 0;
	i++;
	if(s[i]!='O'&&s[i]!='o')return 0;
	i++;
	while(s[i]){
		if(!strchr(" \t\r\n",s[i]))return 0;
		i++;
	}
	return 1;
}

CS_RETCODE FetchData(FILE*out,CS_COMMAND *cmd,int res_type){
	CS_RETCODE		retcode;
	CS_INT			num_cols;
	CS_INT			i;
	CS_INT			row_count = 0;
	CS_INT			rows_read;
	CS_DATAFMT		datafmt;
	COLUMNDATA		*coldata=NULL;
	
	RET_ON_FAIL(ct_res_info(cmd, CS_NUMDATA, &num_cols, CS_UNUSED, NULL),"FetchData: ct_res_info() failed");

	if (num_cols <= 0){
		printf("FetchData: ct_res_info() returned zero columns\n");
		return CS_FAIL;
	}
	coldata=(COLUMNDATA*)malloc(num_cols * sizeof (COLUMNDATA));
	if (coldata == NULL){
		printf("FetchData: malloc() failed\n");
		return CS_FAIL;
	}
	//RES(hwnd,res_type,num_cols);
	for (i = 0; i < num_cols; i++){
		retcode = ct_describe(cmd, (i + 1), &datafmt);
		if (retcode != CS_SUCCEED){
			printf("FetchData: ct_describe() failed\n");
			break;
		}
		datafmt.maxlength = MAX_VALUE_BUF;
		datafmt.datatype = CS_CHAR_TYPE;
		datafmt.format   = CS_FMT_NULLTERM;
		datafmt.count=1;
		retcode = ct_bind(cmd, i+1, &datafmt,coldata[i].val, &coldata[i].len,&coldata[i].ind);
		if (retcode != CS_SUCCEED){
			printf("FetchData: ct_bind() failed\n");
			break;
		}
	}
	
	if (retcode != CS_SUCCEED){
		free(coldata);
		return retcode;
	}

	/*
	** Fetch the rows.  Loop while ct_fetch() returns CS_SUCCEED or 
	** CS_ROW_FAIL
	*/
	while (((retcode = ct_fetch(cmd, CS_UNUSED, CS_UNUSED, CS_UNUSED,
			&rows_read)) == CS_SUCCEED) || (retcode == CS_ROW_FAIL))
	{
		row_count = row_count + rows_read;
		if (retcode == CS_ROW_FAIL){
			printf("Error fetching row %i.\n",row_count);
		}
		if(rows_read==1){
			for (i = 0; i < num_cols; i++){
				if(coldata[i].ind==-1){
				}else{
					//print only row results
					if(res_type==CS_ROW_RESULT)fprintf(out,"%s%s",coldata[i].val,(colbreak&&i<num_cols-1?"\t":""));
				}
			}
			if(res_type==CS_ROW_RESULT&&linebreak)fputc('\n',out);
		}
	}

	free(coldata);

	switch ((int)retcode){
		case CS_END_DATA:
			retcode = CS_SUCCEED;
			break;

		case CS_FAIL:
			printf("FetchData: ct_fetch() failed\n");
			return retcode;

		default:
			printf("FetchData: ct_fetch() returned an expected retcode\n");
			return retcode;
	}

	return retcode;
}


CS_RETCODE HandleResults(FILE*out,CS_COMMAND *cmd){
	CS_RETCODE	retcode;
	CS_INT		res_type;
	
	while( (retcode=ct_results(cmd, &res_type))==CS_SUCCEED ){
		switch(res_type){
			case CS_CMD_SUCCEED:
				break;
			case CS_CMD_DONE:
				break;
			case CS_CMD_FAIL:
				ct_cancel(NULL, cmd, CS_CANCEL_ALL);
				return CS_FAIL;
				
				
			case CS_STATUS_RESULT:
				FetchData(out,cmd,res_type);
				break;
			case CS_PARAM_RESULT:
				FetchData(out,cmd,res_type);
				break;
			case CS_ROW_RESULT:
				FetchData(out,cmd,res_type);
				break;
				
			default:
				printf("ct_results returned unexpected result type: %i",res_type);
				ct_cancel(NULL, cmd, CS_CANCEL_ALL);
				return CS_FAIL;
		}
	}
	if(retcode!=CS_END_RESULTS){
		printf("ct_results() returns unexpected code: %i",retcode);
		return CS_FAIL;
	}
	return CS_SUCCEED;
}



int main(int argc, char* argv[]){
	char *charset=NULL;
	char *srv=NULL;
	char *uid=NULL;
	char *pwd=NULL;
	char *database=NULL;
	char *iname=NULL;
	char *oname=NULL;
	FILE *ifile=NULL;
	FILE *ofile=NULL;
	char buf[4096];
	
	int ret=0;
	
	int i;
	
	CS_CONTEXT         *context=NULL;	/* Context structure     */
	CS_CONNECTION      *connection=NULL;	/* Connection structure. */
	CS_COMMAND         *cmd=NULL;/* Command structure.    */
	CS_LOCALE          *locale=NULL;
	
	
	for(i=1;i<argc;i++){
		if(argv[i][0]!='-')return help();
		switch(argv[i][1]){
			case '?':return help();
			case 'h':return help();
			case 'S':{
				srv=argv[i]+2;
				break;
			}
			case 'U':{
				uid=argv[i]+2;
				break;
			}
			case 'P':{
				pwd=argv[i]+2;
				break;
			}
			case 'D':{
				database=argv[i]+2;
				break;
			}
			case 'i':{
				iname=argv[i]+2;
				break;
			}
			case 'o':{
				oname=argv[i]+2;
				break;
			}
			case 'l':{
				linebreak=1;
				break;
			}
			case 't':{
				colbreak=1;
				break;
			}
			case 'x':{
				exitonerror=1;
				break;
			}
			case 'J':{
				charset=argv[i]+2;
				break;
			}
			default:return help();
		}
	}
	if(!(srv&&uid&&pwd&&database&&iname))return help();
	
	ERR_ON_FAIL(cs_ctx_alloc(CS_VERSION_125, &context), "cs_ctx_alloc failed");
	ERR_ON_FAIL(ct_init(context, CS_VERSION_125), "ct_init failed");

	ifile = fopen( iname, "rb");
	if(ifile==NULL){printf("error: can't open input file \"%s\"",iname);goto error;}
	
	if(oname)ofile = fopen( oname, "wb");
	else ofile=stdout;
	if(ofile==NULL){printf("error: can't open output file \"%s\"",oname);goto error;}

	/*
	** Install a callback function to handle Client-Library errors.
	** 
	** The client message callback receives error or informational
	** messages discovered by Client-Library.
	*/
	ERR_ON_FAIL(ct_callback(context, NULL, CS_SET, CS_CLIENTMSG_CB,
			  (CS_VOID *)clientmsg_callback),
		     "ct_callback for client messages failed");

	/*
	** The server message callback receives server messages sent by
	** the server. These are error or inforamational messages.
	*/
	ERR_ON_FAIL(ct_callback(context, NULL, CS_SET, CS_SERVERMSG_CB,
			  (CS_VOID *)servermsg_callback),
		     "ct_callback for server messages failed");

	if(charset){
		if(CS_SUCCEED!=cs_loc_alloc(context, &locale)){
			printf("cs_loc_alloc() failed\n");
			goto error;
		}
		if(CS_SUCCEED!=cs_locale(context, CS_SET, locale, CS_SYB_CHARSET, charset,CS_NULLTERM, NULL)){
			printf("cs_locale(CS_SET) failed\n");
			goto error;
		}
		if(CS_SUCCEED!=cs_config(context, CS_SET, CS_LOC_PROP, locale,CS_UNUSED, NULL)){
			printf("cs_config(CS_LOC_PROP) failed\n");
			goto error;
		}
	}

	ERR_ON_FAIL(ct_con_alloc(context, &connection), "ct_con_alloc() failed");
	//set user and pass
	ERR_ON_FAIL(ct_con_props(connection, CS_SET, CS_USERNAME,uid,CS_NULLTERM,NULL), "Could not set user name");
	ERR_ON_FAIL(ct_con_props(connection, CS_SET, CS_PASSWORD,pwd, CS_NULLTERM, NULL), "Could not set password");
	ERR_ON_FAIL(ct_con_props(connection, CS_SET, CS_APPNAME,"cisql", CS_NULLTERM, NULL), "Could not set appname");
	//connect
	ERR_ON_FAIL(ct_connect(connection, srv, CS_NULLTERM), "Could not connect!");

	//------------------------------------
	
	
	printf("connected\n");

	ERR_ON_FAIL(ct_cmd_alloc(connection, &cmd), "ct_cmd_alloc() failed");
	
	//use database
	ERR_ON_FAIL(ct_command(cmd,CS_LANG_CMD,"use ",CS_NULLTERM,CS_MORE),"ct_command(CS_LANG_CMD) failed");
	ERR_ON_FAIL(ct_command(cmd,CS_LANG_CMD,database,CS_NULLTERM,CS_END),"ct_command(CS_LANG_CMD) failed");
	ERR_ON_FAIL(ct_send(cmd),"ct_send() failed");
	ERR_ON_FAIL(HandleResults(ofile,cmd),"HandleResults() failed");

	
	while(fgets(buf,sizeof(buf),ifile)){
		if(isGO(buf)){
			ERR_ON_FAIL(ct_command(cmd,CS_LANG_CMD," ",CS_NULLTERM,CS_END),"ct_command(CS_LANG_CMD) failed");
			ERR_ON_FAIL(ct_send(cmd),"ct_send() failed");
			ER0_ON_FAIL(HandleResults(ofile,cmd),"HandleResults() failed");
			cmdstartline=linenumber+1;
		}else{
			ERR_ON_FAIL(ct_command(cmd,CS_LANG_CMD,buf,CS_NULLTERM,CS_MORE),"ct_command(CS_LANG_CMD) failed");
		}
		if(strchr(buf,'\n'))linenumber++;
	}
	ERR_ON_FAIL(ct_command(cmd,CS_LANG_CMD," ",CS_NULLTERM,CS_END),"ct_command(CS_LANG_CMD) failed");
	ERR_ON_FAIL(ct_send(cmd),"ct_send() failed");
	ER0_ON_FAIL(HandleResults(ofile,cmd),"HandleResults() failed");
		
	if(errorcount)ret=1;

	//------------------------------------
	goto end;
	error:
	ret=1;
	end:
	if(cmd){
		ct_cmd_drop(cmd);
		cmd=NULL;
	}
	
	if(connection){
		ct_close(connection, CS_UNUSED);
		ct_con_drop(connection);
		connection=NULL;
	}
	
	if(ifile){fclose(ifile);ifile=NULL;}
	if(ofile){fflush(ofile);fclose(ofile);ofile=NULL;}
	
	if(locale)cs_loc_drop(context, locale);
	
	if(context){
		ct_exit(context, CS_UNUSED);
		cs_ctx_drop(context);
		context=NULL;
	}
	
	
	return ret;
}
